
#include "Triangle.h"

#include "Buffers.h"


#ifndef UTILS_MODEL
#define UTILS_MODEL

#include <unordered_map>

#include <OBJ_Loader.h>

class Model {};

class TriangleMesh {
public:
    std::vector<Triangle3D> triangles;
    std::shared_ptr<Material> material;
    Bounds3f bounds = Bounds3f(ZeroVector3f);

    void load(float* vertices, const int vertexCount, const std::shared_ptr<Material>& _material) {
        material = _material;

        int stride = 8 * 3;
        for (int i = 0; i < vertexCount / 3; ++i) {
            auto triangle = Triangle3D(
                Vector3f{ vertices[i * stride + 0], vertices[i * stride + 1], vertices[i * stride + 2] },
                Vector3f{ vertices[i * stride + 8], vertices[i * stride + 9], vertices[i * stride + 10] },
                Vector3f{ vertices[i * stride + 16], vertices[i * stride + 17], vertices[i * stride + 18] },

                Normalize(Vector3f{ vertices[i * stride + 3], vertices[i * stride + 4], vertices[i * stride + 5] }),
                Normalize(Vector3f{ vertices[i * stride + 11], vertices[i * stride + 12], vertices[i * stride + 13] }),
                Normalize(Vector3f{ vertices[i * stride + 19], vertices[i * stride + 20], vertices[i * stride + 21] }),
                
                Vector2f{ vertices[i * stride + 6], vertices[i * stride + 7] },
                Vector2f{ vertices[i * stride + 14], vertices[i * stride + 15] },
                Vector2f{ vertices[i * stride + 22], vertices[i * stride + 23] },

                _material
            );
            bounds = Union(bounds, triangle.getBounds());
            triangles.push_back(triangle);
        }
    }

    std::shared_ptr<VertexArray> toVAO() {
        auto vao = std::make_shared<VertexArray>();
        auto ibo = std::make_shared<IndexBuffer>();
        ibo->setAllBuffer(nullptr, 3 * triangles.size(), LoadBufferMode::StaticDraw);
        vao->setIndexBuffer(ibo);

        std::vector<float> vertices;
        auto vbo = std::make_shared<VertexBuffer>();
        vbo->setLayout({
            { "Positions",  VertexElementType::Vec3f, false },
            { "Normals",    VertexElementType::Vec3f, false },
            { "TexCoords",  VertexElementType::Vec2f, false }
        });
        for (auto& triangle : triangles) {
            vertices.push_back(triangle.v0[0]);
            vertices.push_back(triangle.v0[1]);
            vertices.push_back(triangle.v0[2]);

            vertices.push_back(triangle.n0[0]);
            vertices.push_back(triangle.n0[1]);
            vertices.push_back(triangle.n0[2]);

            vertices.push_back(triangle.t0[0]);
            vertices.push_back(triangle.t0[1]);


            vertices.push_back(triangle.v1[0]);
            vertices.push_back(triangle.v1[1]);
            vertices.push_back(triangle.v1[2]);

            vertices.push_back(triangle.n1[0]);
            vertices.push_back(triangle.n1[1]);
            vertices.push_back(triangle.n1[2]);

            vertices.push_back(triangle.t1[0]);
            vertices.push_back(triangle.t1[1]);


            vertices.push_back(triangle.v2[0]);
            vertices.push_back(triangle.v2[1]);
            vertices.push_back(triangle.v2[2]);

            vertices.push_back(triangle.n2[0]);
            vertices.push_back(triangle.n2[1]);
            vertices.push_back(triangle.n2[2]);

            vertices.push_back(triangle.t2[0]);
            vertices.push_back(triangle.t2[1]);
        }
        vbo->setAllBuffer(&vertices[0], vertices.size() * sizeof(float), LoadBufferMode::StaticDraw, BatchBufferMode::Interleave,  0);
        vao->addVertexBuffer(vbo);

        vao->unbind();
        return vao;
    }
};

class TriangleModel : public Model {
public:
    std::vector<TriangleMesh> meshes;
    Bounds3f bounds = Bounds3f(ZeroVector3f);
    Matrix4f transform = Matrix4f(1.f);

    void recalculateBounds() {
        for (auto& mesh : meshes)
            bounds = Union(bounds, mesh.bounds);
    }

    void load(const std::string& path) {
        objl::Loader Loader;
        bool loadout = Loader.LoadFile(path);

        std::unordered_map<std::string, std::shared_ptr<Material>> materials;

        std::string mtlRootPath = path.substr(0, path.find_last_of("/\\") + 1);

        for(auto& mesh : Loader.LoadedMeshes) {
            auto mtl = mesh.MeshMaterial;

            TriangleMesh triangleMesh;
            triangleMesh.material = Material::DefaultDiffuseMaterial();
            
            if (!mtl.name.empty()) {
                if (materials.end() == materials.find(mtl.name)) {
                    materials[mtl.name] = triangleMesh.material;
                    
                    triangleMesh.material->name = mtl.name;
                    triangleMesh.material->Ka = Vector3f{ mtl.Ka.X, mtl.Ka.Y, mtl.Ka.Z };
                    triangleMesh.material->Kd = Vector3f{ mtl.Kd.X, mtl.Kd.Y, mtl.Kd.Z };
                    triangleMesh.material->Ks = Vector3f{ mtl.Ks.X, mtl.Ks.Y, mtl.Ks.Z };
                    triangleMesh.material->Ns = mtl.Ns;
                    triangleMesh.material->Ni = mtl.Ni;
                    triangleMesh.material->d = mtl.d;
                    triangleMesh.material->illum = mtl.illum;

                    if (!mtl.map_Ka.empty()) {
                        auto map_ka = std::make_shared<Texture>();
                        map_ka->load((mtlRootPath + mtl.map_Ka).c_str(), true);
                        triangleMesh.material->map_Ka = map_ka;
                    }

                    if (!mtl.map_Kd.empty()) {
                        auto map_kd = std::make_shared<Texture>();
                        map_kd->load((mtlRootPath + mtl.map_Kd).c_str(), true);
                        triangleMesh.material->map_Kd = map_kd;
                    }

                    if (!mtl.map_Ks.empty()) {
                        auto map_ks = std::make_shared<Texture>();
                        map_ks->load((mtlRootPath + mtl.map_Ks).c_str(), true);
                        triangleMesh.material->map_Ks = map_ks;
                    }

                    if (!mtl.map_Ns.empty()) {
                        auto map_ns = std::make_shared<Texture>();
                        map_ns->load((mtlRootPath + mtl.map_Ns).c_str(), true);
                        triangleMesh.material->map_Ns = map_ns;
                    }

                    if (!mtl.map_d.empty()) {
                        auto map_d = std::make_shared<Texture>();
                        map_d->load((mtlRootPath + mtl.map_d).c_str(), true);
                        triangleMesh.material->map_d = map_d;
                    }

                    if (!mtl.map_bump.empty()) {
                        auto map_bump = std::make_shared<Texture>();
                        map_bump->load((mtlRootPath + mtl.map_bump).c_str(), true);
                        triangleMesh.material->map_bump = map_bump;
                    }
                }
                else {
                    triangleMesh.material = materials[mtl.name];
                }
            }

            for(int i = 0; i < mesh.Vertices.size(); i += 3) {
                auto triangle = Triangle3D({
                    Vector3f{ mesh.Vertices[i    ].Position.X, mesh.Vertices[i    ].Position.Y, mesh.Vertices[i    ].Position.Z },
                    Vector3f{ mesh.Vertices[i + 1].Position.X, mesh.Vertices[i + 1].Position.Y, mesh.Vertices[i + 1].Position.Z },
                    Vector3f{ mesh.Vertices[i + 2].Position.X, mesh.Vertices[i + 2].Position.Y, mesh.Vertices[i + 2].Position.Z },

                    Normalize(Vector3f{ mesh.Vertices[i    ].Normal.X, mesh.Vertices[i    ].Normal.Y, mesh.Vertices[i    ].Normal.Z }),
                    Normalize(Vector3f{ mesh.Vertices[i + 1].Normal.X, mesh.Vertices[i + 1].Normal.Y, mesh.Vertices[i + 1].Normal.Z }),
                    Normalize(Vector3f{ mesh.Vertices[i + 2].Normal.X, mesh.Vertices[i + 2].Normal.Y, mesh.Vertices[i + 2].Normal.Z }),

                    Vector2f{ mesh.Vertices[i    ].TextureCoordinate.X, mesh.Vertices[i    ].TextureCoordinate.Y },
                    Vector2f{ mesh.Vertices[i + 1].TextureCoordinate.X, mesh.Vertices[i + 1].TextureCoordinate.Y },
                    Vector2f{ mesh.Vertices[i + 2].TextureCoordinate.X, mesh.Vertices[i + 2].TextureCoordinate.Y },

                    triangleMesh.material
                });
                bounds = Union(bounds, triangle.getBounds());
                triangleMesh.triangles.push_back(triangle);
            }

            meshes.push_back(triangleMesh);
        }
    }
};

#endif // UTILS_MODEL
